package model;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;

/**
 * 
 * @author morellatoariel
 *
 */
public class SistemaWeb {
	
	private HashMap<String, Usuario> usuariosRegistrados= new HashMap<String, Usuario>();	// userName | usuario
	private HashMap<String, String> passwords= new HashMap<String, String>();	// userName | password
	private ArrayList<CFP> cfps = new ArrayList<CFP>();
	private AlgoritmoDeAsignacion algoritmoDeAsignacionDeRevisoresATrabajos;
	private ArrayList<Usuario> usuariosBorrados = new ArrayList<Usuario>();
	private List<String> areasDeInteres = new ArrayList<String>();
	private ManejadorDeEstados manejadorDeEstados;
	
	/**
	 * 
	 * @param algoritmo
	 * @param areas
	 * @param manejador
	 */
	public void inicializar(AlgoritmoDeAsignacion algoritmo, List<String> areas, ManejadorDeEstados manejador){
		this.algoritmoDeAsignacionDeRevisoresATrabajos= algoritmo;
		this.areasDeInteres= areas;
		this.manejadorDeEstados= manejador;
	}
	
// Accesos 
	
	public ManejadorDeEstados getManejadorDeEstados(){
		return this.manejadorDeEstados;
	}
	
	public ArrayList<CFP> getCfps(){
		return this.cfps;
	}
	
	
	public List<String> getAreasDeInteres(){
		return this.areasDeInteres;
	}
	
	
	public AlgoritmoDeAsignacion getAlgoritmoDeAsignacionDeRevisores(){
		return this.algoritmoDeAsignacionDeRevisoresATrabajos;
	}

	
	public HashMap<String, Usuario> getUsuariosRegistrados(){
		return this.usuariosRegistrados;
	}
	
	public HashMap<String, String> getPasswords(){
		return this.passwords;
	}
	
	public ArrayList<Usuario> getUsuariosBorrados(){
		return this.usuariosBorrados;
	}

	
// Modificadores en las estructuras
	
	public void agregarUsuarioRegistrado(Usuario usuario){
		this.getUsuariosRegistrados().put(usuario.getUserName(), usuario);
		this.getPasswords().put(usuario.getUserName(), usuario.getPassword());
	}
	
	public void agregarCfp(CFP cfp){
		this.getCfps().add(cfp);
	}
	
		// borra al usuario de registrados y lo agrega en borrados.
	public void borrarUsuarioDeRegistrado(Usuario usuario){
		this.getUsuariosRegistrados().remove(usuario.getUserName());
		this.usuariosBorrados.add(usuario);
	}
	

	/**
	 * Lista todos los usuarios registrados para que el creador del CFP eliga los revisores. 
	 * La lista debe estar ordenada en base a la afinidad de los usuario con un CFP. 
	 * La afinidada viene dada por la cantidad de areas en las que es experto un usuario y las areas de interes de un CFP.
	 * @param List<String> areas.
	 * @return La lista de usuarios.
	 */
	public ArrayList<Usuario> listarUsuarios(List<String> areas){
		// el elemento con indice x en cantAreasEnComun machea con el elemento de indice x en usuariosDelSistema
		Usuario[] usuariosDelSistema= this.generarListaDeUsuarios().toArray(new Usuario[this.usuariosRegistrados.size()]);
		ArrayList<Integer> cantAreasEnComun= new ArrayList<Integer>();
		for(int i=0;i<usuariosDelSistema.length;i++){
			cantAreasEnComun.add(i, this.areasEnComun(areas,usuariosDelSistema[i].getAreasDeExperiencia()));
		}
		
		ArrayList<Usuario> resultado= new ArrayList<Usuario>();
		
		while (usuariosDelSistema.length != 0){
			int indiceDelMaximo=cantAreasEnComun.indexOf(Collections.max(cantAreasEnComun));
			cantAreasEnComun.remove(indiceDelMaximo);
			resultado.add(usuariosDelSistema[indiceDelMaximo]);	
			usuariosDelSistema= this.borrarDelArray(usuariosDelSistema,usuariosDelSistema[indiceDelMaximo]);
		}
		return resultado;
	}
	
	/**
	 * Recibe un array y un elemento a borrar
	 * @param usuarios
	 * @param usuarioABorrar
	 * @return El array recibido sin el elemento a borrar
	 */
	private Usuario[] borrarDelArray(Usuario[] usuarios,Usuario usuarioABorrar){
		ArrayList<Usuario> resParcial= new ArrayList<Usuario>();
		for(Usuario us : usuarios){
			if(us != usuarioABorrar){
				resParcial.add(us);
			}
		}
		return resParcial.toArray(new Usuario[resParcial.size()]);
		
	}

	/**
	 * Retorna la lista de usuarios ignorando los userNames
	 * @return
	 */
	private Collection<Usuario> generarListaDeUsuarios(){
		HashMap<String, Usuario> usuariosEnHash= this.getUsuariosRegistrados();
		Collection<Usuario> resultado= usuariosEnHash.values();
		return resultado;
		
	}
	
	/**
	 * 
	 * @param areasDelCfp
	 * @param areasDeUsuario
	 * @return La cantidad de areas que tengan en comun las dos listas de areas recibidas.
	 */
	public int areasEnComun(List<String> areasDelCfp, List<String> areasDeUsuario){
		int res=0;
		for( String areaUsuario : areasDeUsuario){
			if(areasDelCfp.contains(areaUsuario)){
				res++;
			}
		}
		return res;
	}

	/**
	 * valida si el autor del tp es el usuario que lo envia. 
	 * verfica que no se haya pasado de la fecha de recepcion. 
	 * setea el manejador de estados en el trabajo.
	 * asigna el tp al CFP elegido. 
	 * @param cfp
	 * @param tp
	 * @param usuario
	 * @return void
	 * @throws NoEsTuTrabajoException si el autor del trabajo que se envia no coincide con el usuario que lo envia.
	 * @throws CaducoFechaDeEnvioException si el cfp al que se envia el tp ya no recibe mas trabajos porque se cumplio la fecha tope de recepcion.
	 */
	public void enviarTrabajo(CFP cfp, Trabajo tp, Usuario usuario) throws NoEsTuTrabajoException,CaducoFechaDeEnvioException{
		if(usuario.esMiTrabajo(tp)){
			if(!cfp.caducoFechaDeEnvío()){
				tp.setManejadorDeEstado(this.manejadorDeEstados);
				cfp.agregarTrabajo(tp,usuario);	// agrega el trabajo al cpf
			}
			else{
				throw new CaducoFechaDeEnvioException("Caduco la fecha de envio para este CFP");
			}
		}
		else{
			throw new NoEsTuTrabajoException("No eres autor del trabajo que envias");
		}
	}
	
	
	/**
	 * Aca se crean los CFP
	 * @param areas
	 * @param nombre
	 * @param fechaTopeRevision
	 * @param fechaTopeRecepcion
	 * @param autor
	 * @return
	 * @throws CamposEnNullException si hay al menos algun parametro vacio o null.
	 */
	public CFP crearCFP(List<String> areas, String nombre, Date fechaTopeRevision, Date fechaTopeRecepcion, Usuario autor) throws CamposEnNullException{
		if(!this.hayCamposNull(areas,nombre,fechaTopeRevision,fechaTopeRecepcion,autor)){
				CFP cfp= new CFP(autor,nombre,areas,fechaTopeRecepcion,fechaTopeRevision,this.listarUsuarios(areas));
				cfp.activarTemporizador(fechaTopeRecepcion,this,cfp);
				this.agregarCfp(cfp);
				return cfp;
			}
		else{
			throw new CamposEnNullException("Todos los campos obligatorios deben completarse");
		}
	}

	private boolean hayCamposNull(List<String> areas,String nombre, Date fechaRev, Date fechaRec, Usuario autor){
	return areas.isEmpty() || nombre == null || fechaRec == null || fechaRev == null || autor == null;
	}
		
	
	public ArrayList<CFP> listarCFP(){
		return this.getCfps();
	}
	
	/**
	 * @param nombre
	 * @param apellido
	 * @param userName
	 * @param password
	 * @param areasExperiencia
	 * @param casillaDeCorreo
	 * @throws UserNameRepetidoException
	 * @throws PasswordCortoException
	 * Observaciones: Ningun parametro puede ser null o vacio. El password debe tener una longitud mayor a 4.
	 */
	public void registrarse(String nombre, String apellido, String userName, String password,
			List<String> areasExperiencia, CasillaDeCorreo casillaDeCorreo)throws UserNameRepetidoException,PasswordCortoException{
		if (!this.existUserName(userName)){
			if(password.length()>4){
				Usuario usuario= new Usuario(nombre, apellido, userName, password, areasExperiencia, casillaDeCorreo);
				this.agregarUsuarioRegistrado(usuario);
			}
			else{
				throw new PasswordCortoException();
			}
		}
		else{
			throw new UserNameRepetidoException("El userName ingresado ya existe");
		}
	}
	
	/**
	 * @param userName
	 * @return Si el userName ya pertenece al dominio de userNames del sistema.
	 */
	private boolean existUserName(String userName){
		return this.getUsuariosRegistrados().containsKey(userName);
	}
	
	/**
	 * Se genera un mail y se lo envia al usuario recibido por parametro.
	 * @param usuario
	 * @param tp
	 */
	public void enviarMail(Usuario usuario, Trabajo tp){
		Mail mail = new Mail(tp);
		usuario.getCasillaDeCorreo().enviarMail(mail);
	}
	
	/**
	 * Este mensaje es enviado cuando el trabajo se corrigio, por eso incluye la nota final del trabajo.
	 * @param usuario
	 * @param tp
	 * @param nota
	 */
	public void enviarMail(Usuario usuario, Trabajo tp, EstadoDeCorreccion nota){
		Mail mail = new Mail(tp, nota);
		usuario.getCasillaDeCorreo().enviarMail(mail);
	}
	
	/**
	 * Ejecuta su algoritmo de asignacion.
	 * @param cfp
	 */
	public void asignacionAutomaticaDeRevisoresATrabajos(CFP cfp) {
		
		this.algoritmoDeAsignacionDeRevisoresATrabajos.ejecutate(cfp, this);
	}

	
	

}
